package main

import (
	"ID3"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

/**
歌曲查询
http://www.xiami.com/song/playlist/id/1770446157/object_name/default/object_id/0/cat/json
专辑查询
http://www.xiami.com/song/playlist/id/462116/type/1/cat/json
精选集
www.xiami.com/song/playlist/id/27742416/type/3/cat/json
**/
var outputDirPath string = ""

func main() {

	var (
		downloadUrl = flag.String("u", "", "虾米网页地址")

		outputDir = flag.String("o", filepath.Join(GetCurrentDir(), "download"), "输出目录")
	)
	if !flag.Parsed() {
		flag.Parse()
	}
	if *downloadUrl == "" || *outputDir == "" {
		flag.Usage()
		os.Exit(1)
	}
	outputDirPath = *outputDir

	downloadByUrl(*downloadUrl)
}

type Track struct {
	Title      string
	Album_name string
	Artist     string
	Location   string
	Lyric      string
}

type DataStruct struct {
	TrackList []Track
}
type TopStruct struct {
	Data DataStruct
}

func downloadByUrl(url string) {
	numberRegexp, _ := regexp.Compile("(\\d+)")
	id := numberRegexp.Find([]byte(url))
	if id == nil {
		return
	}
	if strings.Contains(url, "album") {
		downloadAlbum(string(id))
	}
	if strings.Contains(url, "song") {
		downloadSong(string(id))
	}
	if strings.Contains(url, "showcollect") {
		downloadCollect(string(id))
	}
}

func downloadCollect(collectId string) {
	url := "http://www.xiami.com/song/playlist/id/${id}/type/3/cat/json"
	url = strings.Replace(url, "${id}", collectId, -1)
	log.Println(url)
	jsonBack := NetGetString(url)
	var data TopStruct
	json.Unmarshal(jsonBack, &data)
	download(data)
}

func downloadAlbum(albumId string) {
	url := "http://www.xiami.com/song/playlist/id/${albumId}/type/1/cat/json"
	url = strings.Replace(url, "${albumId}", albumId, -1)
	jsonBack := NetGetString(url)
	var data TopStruct
	json.Unmarshal(jsonBack, &data)
	download(data)
}

func downloadSong(songId string) {
	url := "http://www.xiami.com/song/playlist/id/${songId}/object_name/default/object_id/0/cat/json"
	url = strings.Replace(url, "${songId}", songId, -1)
	jsonBack := NetGetString(url)
	var data TopStruct
	json.Unmarshal(jsonBack, &data)
	download(data)

}

func NetGetString(url string) []byte {
	resp, _ := http.Get(url)
	outputReader := resp.Body
	defer outputReader.Close()
	allData, _ := ioutil.ReadAll(outputReader)
	return allData
}

func download(data TopStruct) {

	for _, song := range data.Data.TrackList {
		fileName := getFilename(song)
		tmpFileName := fmt.Sprintf("%s.tmp", fileName)
		targetFileName := filepath.Join(outputDirPath, tmpFileName)
		log.Println("download songs to ", filepath.Join(outputDirPath, fileName))
		downloadFile(decodeLocation(song.Location), targetFileName)
		AddId3(targetFileName, filepath.Join(outputDirPath, fileName), song)
		os.Remove(targetFileName)

	}
}

func AddId3(tmpPath, targetPath string, song Track) {
	metaLenth := getId3Length(tmpPath)
	var id3Meta ID3.Id3v23Info
	id3Meta.Set(ID3.FRAME_TYPE_TITLE, []byte(song.Title))
	id3Meta.Set(ID3.FRAME_TYPE_ARTIST, []byte(song.Artist))
	id3Meta.Set(ID3.FRAME_TYPE_ALBUM_TITLE, []byte(song.Album_name))
	input, err := os.OpenFile(tmpPath, os.O_RDONLY, os.ModePerm)
	if err != nil {
		return
	}
	defer input.Close()
	output, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		return
	}
	defer output.Close()
	input.Seek(metaLenth, 0)
	output.Write(id3Meta.ToByties())

	buf := make([]byte, 100*1024)
	for {
		rl, err := input.Read(buf)
		if err != nil || rl <= 0 {
			break
		}
		output.Write(buf[:rl])
	}

}

func getId3Length(path string) int64 {
	file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
	if err != nil {
		return 0
	}
	defer file.Close()
	buf := make([]byte, 10)
	file.Read(buf)
	return int64(ID3.GetID3V23Length(buf))
}

func getFilename(track Track) string {
	ret := fmt.Sprintf("%s-%s", track.Artist, track.Title)
	//ret = strings.Replace(ret, ".", " ", -1)
	ret = strings.Replace(ret, "\\", " ", -1)
	ret = strings.Replace(ret, "/", " ", -1)
	ret = strings.Replace(ret, "|", " ", -1)
	ret = strings.Replace(ret, "&", " ", -1)
	return fmt.Sprintf("%s.mp3", ret)

}

func downloadFile(url, target string) {
	resp, _ := http.Get(url)
	outputReader := resp.Body
	defer outputReader.Close()
	os.MkdirAll(filepath.Dir(target), os.ModePerm)
	outputfile, _ := os.OpenFile(target, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	defer outputfile.Close()
	buffer := make([]byte, 8*1024)

	for {
		readLen, err := outputReader.Read(buffer)
		if readLen <= 0 || err != nil {
			break
		}
		outputfile.Write(buffer[0:readLen])
	}

}

func GetCurrentDir() string {
	absPath, _ := filepath.Abs(os.Args[0])
	pathDir := filepath.Dir(absPath)
	return pathDir
}

func decodeLocation(input string) string {
	data := []byte(input)

	lineNumber := int(data[0] - '0')
	dataLen := len(data) - 1
	div := dataLen / lineNumber
	mod := dataLen % lineNumber

	maxColLen := div
	if mod > 0 {
		maxColLen++
	}

	var martix [][]byte
	martix = make([][]byte, lineNumber)

	mm := mod
	idx := 1
	for line := 0; line < lineNumber; line++ {
		lineLen := div
		if mm > 0 {
			lineLen++
		}
		martix[line] = make([]byte, maxColLen, maxColLen)
		copy(martix[line], data[idx:idx+lineLen])
		//log.Println(string(martix[line]))
		mm--
		idx += lineLen
	}

	newStr := make([]byte, 0)
	for col := 0; col < maxColLen; col++ {
		for row := 0; row < lineNumber; row++ {
			c := martix[row][col]
			if c == 0 {
				col = maxColLen
				break
			}
			newStr = append(newStr, c)
		}
	}

	finalStr := strings.Replace(string(newStr), "%5E", "0", -1)
	finalStr, _ = url.QueryUnescape(finalStr)
	//log.Printf("%d %d %d", lineNumber, div, mod)
	//log.Println(finalStr)
	return finalStr

}
